package com.pft.web.view.presenter;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ResultParser;
import com.google.zxing.common.HybridBinarizer;
import com.pft.web.view.contract.QrCodeContract;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;

@SuppressWarnings("deprecation")
public class QrCodePresenterImp implements QrCodeContract.Presenter {

    public static final int FORMATS_FOR_MODE_1D = 1;

    public static final int FORMATS_FOR_MODE_2D = 2;

    private static final int SCALE_BITMAP_MAX_WIDTH = 1000;

    private static final int SCALE_BITMAP_MAX_HEIGHT = 1000;

    private QrCodeContract.View mView;

    private Camera mCamera;

    private int cameraOrientation;

    private boolean active;

    private int previewWidth, previewHeight;

    private Rect frameRect;

    private int barcodeWidth, barcodeHeight;

    private Map<DecodeHintType, Object> mHints;

    private Handler mHandler = new Handler(Looper.getMainLooper());

    public QrCodePresenterImp(QrCodeContract.View view) {
        mView = view;
    }

    @Override
    public void openCamera() {
        try {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
                Camera.getCameraInfo(i, cameraInfo);
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    mCamera = Camera.open(i);
                    cameraOrientation = cameraInfo.orientation;
                    mCamera.setDisplayOrientation(cameraOrientation);
                }
            }
        } catch (Throwable t) {
            closeCamera();
            mView.openCameraError(t);
        }
    }

    @Override
    public void setupCamera(int previewWidth, int previewHeight, Rect frameRect, SurfaceHolder holder) {
        if (mCamera != null) {
            this.previewWidth = previewWidth;
            this.previewHeight = previewHeight;
            this.frameRect = frameRect;

            try {
                setErrorCallback();
                setPreviewDisplay(holder);
                setCameraParameters();
                setDecodeFormat();
            } catch (Throwable t) {
                closeCamera();
                mView.openCameraError(t);
            }
        }
    }

    public void setErrorCallback() {
        mCamera.setErrorCallback(new Camera.ErrorCallback() {
            @Override
            public void onError(int error, Camera camera) {
                closeCamera();
                mView.closePage();
            }
        });
    }

    private void setPreviewDisplay(SurfaceHolder holder) {
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            closeCamera();
            mView.closePage();
        }
    }

    private void setCameraParameters() {
        Camera.Parameters parameters = mCamera.getParameters();

        // 设置闪光灯默认关闭
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);

        // 设置对焦模式
        List<String> focusModesList = parameters.getSupportedFocusModes();
        if (focusModesList != null) {
            String focus = null;
            if (focusModesList.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                focus = Camera.Parameters.FOCUS_MODE_AUTO;
            }
            if (focus != null) {
                parameters.setFocusMode(focus);
            }
        }

        // 设置预览尺寸
        List<Camera.Size> previewSizesList = parameters.getSupportedPreviewSizes();
        float previewRatio = (float) previewWidth / (float) previewHeight;
        float diff = Float.POSITIVE_INFINITY;
        for (int i = 0; i < previewSizesList.size(); i++) {
            int realWidth = previewSizesList.get(i).width;
            int realHeight = previewSizesList.get(i).height;
            int maybeFlippedWidth = realWidth > realHeight ? realHeight : realWidth;
            int maybeFlippedHeight = realWidth > realHeight ? realWidth : realHeight;
            if (previewWidth == maybeFlippedWidth && previewHeight == maybeFlippedHeight) {
                barcodeWidth = realWidth;
                barcodeHeight = realHeight;
                break;
            }
            float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
            float newDiff = Math.abs(aspectRatio - previewRatio);
            if (newDiff < diff) {
                barcodeWidth = realWidth;
                barcodeHeight = realHeight;
                diff = newDiff;
            }
        }
        parameters.setPreviewSize(barcodeWidth, barcodeHeight);

        // 设置预览格式
        parameters.setPreviewFormat(ImageFormat.NV21);

        mCamera.setParameters(parameters);
    }

    /**
     * 设置支持的解码格式
     */
    private void setDecodeFormat() {
        mHints = new EnumMap<>(DecodeHintType.class);
        Collection<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);

        decodeFormats.add(BarcodeFormat.UPC_A);
        decodeFormats.add(BarcodeFormat.UPC_E);
        decodeFormats.add(BarcodeFormat.EAN_13);
        decodeFormats.add(BarcodeFormat.EAN_8);
        decodeFormats.add(BarcodeFormat.RSS_14);
        decodeFormats.add(BarcodeFormat.RSS_EXPANDED);
        decodeFormats.add(BarcodeFormat.CODE_39);
        decodeFormats.add(BarcodeFormat.CODE_93);
        decodeFormats.add(BarcodeFormat.CODE_128);
        decodeFormats.add(BarcodeFormat.ITF);
        decodeFormats.add(BarcodeFormat.CODABAR);


        decodeFormats.add(BarcodeFormat.QR_CODE);

        decodeFormats.add(BarcodeFormat.DATA_MATRIX);
        decodeFormats.add(BarcodeFormat.AZTEC);
        decodeFormats.add(BarcodeFormat.PDF_417);

        mHints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
    }

    @Override
    public void startPreview() {
        if (mCamera != null) {
            active = true;
            mCamera.startPreview();
            autoFocus();
        }
    }

    @Override
    public void stopPreview() {
        if (mCamera != null) {
            cancelAutoFocus();
            mCamera.stopPreview();
            active = false;
        }
    }

    @Override
    public void closeCamera() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    public void flashOn() {
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
        mCamera.setParameters(parameters);
    }

    @Override
    public void flashOff() {
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
        mCamera.setParameters(parameters);
    }

    private void setPreviewCallback() {
        if (mCamera != null && active) {
            mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {

                @Override
                public void onPreviewFrame(final byte[] data, final Camera camera) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            if (mCamera != null && active && data != null) {
                                try {
                                    processData(data);
                                } catch (OutOfMemoryError e) {
                                    System.gc();
                                } catch (Throwable e) {

                                }
                            }
                        }
                    }).start();
                }
            });
        }
    }

    private void autoFocus() {
        if (mCamera != null && active) {
            mCamera.autoFocus(new Camera.AutoFocusCallback() {

                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            setPreviewCallback();
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {

                            }
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    autoFocus();
                                }
                            });
                        }
                    }).start();
                }
            });
        }
    }

    private void cancelAutoFocus() {
        if (mCamera != null) {
            mCamera.cancelAutoFocus();
        }
    }

    private void processData(final byte[] data) {
        Log.e("result", "processData " + data);
        Bitmap bitmap = getBitmap(data);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

        Result rawResult;
        try {
            RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
            MultiFormatReader multiFormatReader = new MultiFormatReader();
            multiFormatReader.setHints(mHints);
            BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
            rawResult = multiFormatReader.decodeWithState(binaryBitmap);
        } catch (ReaderException re) {
            Log.e("result", "NotFoundException" + data);
            rawResult = null;
        }

        if (bitmap.isRecycled()) {
            bitmap.recycle();
        }

        if (rawResult != null) {
            final ParsedResult parsedResult = ResultParser.parseResult(rawResult);
            final BarcodeFormat format = rawResult.getBarcodeFormat();
            if (parsedResult != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (mCamera != null && active) {
                            mView.displayScanResult(getProductFormats(format), parsedResult.getDisplayResult());
                        }
                    }
                });
            }
        }
    }

    private Bitmap getBitmap(final byte[] data) {
        YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, barcodeWidth, barcodeHeight, null);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, out);

        byte[] imageByteArray = out.toByteArray();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length, options);
        options.inSampleSize = calculateInSampleSize(options, SCALE_BITMAP_MAX_WIDTH, SCALE_BITMAP_MAX_HEIGHT);
        options.inJustDecodeBounds = false;
        Bitmap originalBitmap = BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length, options);

        Matrix matrix = new Matrix();
        matrix.setRotate(cameraOrientation);
        Bitmap rotateBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, false);

        int x = frameRect.left * rotateBitmap.getWidth() / previewWidth;
        int y = frameRect.top * rotateBitmap.getHeight() / previewHeight;
        int width = frameRect.width() * rotateBitmap.getWidth() / previewWidth;
        int height = frameRect.height() * rotateBitmap.getHeight() / previewHeight;
        Bitmap cropBitmap = Bitmap.createBitmap(rotateBitmap, x, y, width, height);

        try {
            out.close();
            if (originalBitmap.isRecycled()) {
                originalBitmap.recycle();
            }
            if (rotateBitmap.isRecycled()) {
                rotateBitmap.recycle();
            }
        } catch (IOException e) {

        }

        return cropBitmap;
    }

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;

        while (height > reqHeight || width > reqWidth) {
            inSampleSize = inSampleSize * 2;
            height = height / 2;
            width = width / 2;
        }

        return inSampleSize;
    }

    private int getProductFormats(BarcodeFormat format) {
        if (format.equals(BarcodeFormat.UPC_A) ||
                format.equals(BarcodeFormat.UPC_E) ||
                format.equals(BarcodeFormat.EAN_13) ||
                format.equals(BarcodeFormat.EAN_8) ||
                format.equals(BarcodeFormat.RSS_14) ||
                format.equals(BarcodeFormat.RSS_EXPANDED) ||
                format.equals(BarcodeFormat.CODE_39) ||
                format.equals(BarcodeFormat.CODE_93) ||
                format.equals(BarcodeFormat.CODE_128) ||
                format.equals(BarcodeFormat.ITF) ||
                format.equals(BarcodeFormat.CODABAR)) {
            return FORMATS_FOR_MODE_1D;
        } else {
            return FORMATS_FOR_MODE_2D;
        }
    }

}
